/**
    ATClab - scriptable Air Traffic Control simulations.
    Copyright (C) 2010 openatclab@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

#include "performance.h"
#include "data.h"
#include "exception.h"

#include <sstream>

#ifdef WIN32
	#include <assert.h>
#endif


using namespace performance;

////////////////////////////////////////////////////////////////////////////////
//
// performance::performance
//
//------------------------------------------------------------------------------
//
//

/*!
 *
 */
Performance::Performance( const PDT *type, const PDO *data )
	  : _type( type ), _data( data ) {}

/*!
 *
 */
Performance::~Performance() {}


//------------------------------------------------------------------------------
//
//

/*!
 *
 */
double Performance::cruising_altitude() const {
	throw performance_data_error( 
		"data does not contain cruising altitude information" 
	);
}

/*!
 * Calculate the throttle setting from current cruise speed.
 *
 * The throttle setting is in the range -1.0 to +1.0 where a throttle setting
 * of +1.0 corresponds to the maximum cruise speed, a throttle setting of 0.0
 * corrensponds to the standard cruising speed and -1.0 corresponds to the
 * minimum cruise speed. The throttle is concidered to give linear performance
 * between 0.0 and +/- 1.0 but the +ve and negative parts may not have the same
 * slope.
 *
 * @param z the cuise velocity is altitude dependant
 * @param v the current cruise velocity
 */
double Performance::throttle_setting( double z, double v ) const {
	double min = cruise_speed_minimum( z );
	double std = cruise_speed_standard( z );
	double max = cruise_speed_maximum( z );

	assert( min <= std && std <= max );

	if ( v < min || max < v ) {
		std::ostringstream oss;
		oss 
			 << "invalid cruise speed: " 
			 << "Type( " << _type->type  << " ) "
			 << "@( " << z << " ) "
			 << "!( " << min << " <= " << v << " <= " << max << " )!" 
			 << std::ends;
		throw performance_data_error( oss.str() );
	}

	return 
			(int)( std ) == (int)( v ) ? 0.0 
		  :        std   <         v   ? ( v - std ) / ( max - std )
		  :     /* std   >         v */  ( v - std ) / ( std - min )
		  ;
}

/*!
 *
 */
double Performance::cruise_speed( double z, double t ) const {
	return adjusted_level_value( 
			z, t
		  , &Performance::cruise_speed_standard
		  , &Performance::cruise_speed_minimum
		  , &Performance::cruise_speed_maximum 
	);
}

/*!
 *
 */
double Performance::acceleration( double z, double t ) const {
	double a = acceleration_standard( z );
	return adjusted_value( 
			t, a
		  , reduced_acceleration_factor() * a
		  , maximum_acceleration_factor() * a
	);
}

/*!
 *
 */
double Performance::deceleration( double z, double t ) const {
	double a = deceleration_standard( z );
	return adjusted_value( 
			t, a
		  , reduced_deceleration_factor() * a
		  , maximum_deceleration_factor() * a
	);
}

/*!
 *
 */
double Performance::climb_speed( double z, double t  ) const {
	return adjusted_level_value( 
			z, t
		  , &Performance::climb_speed_standard
		  , &Performance::climb_speed_minimum
		  , &Performance::climb_speed_maximum 
	);
}

/*!
 * @todo reduced:standard:accelerated:maximum
 */
double Performance::climb_rate( double z, double t  ) const {
	return adjusted_level_value( 
			z, t
		  , &Performance::climb_rate_standard
		  , &Performance::climb_rate_reduced
		  , &Performance::climb_rate_maximum 
	);
}

/*!
 *
 */
double Performance::descent_speed( double z, double t  ) const {
	return adjusted_level_value( 
			z, t
		  , &Performance::descent_speed_standard
		  , &Performance::descent_speed_minimum
		  , &Performance::descent_speed_maximum 
	);
}

/*!
 * @todo reduced:standard:accelerated:maximum
 */
double Performance::descent_rate( double z, double t  ) const {
	return adjusted_level_value( 
			z, t
		  , &Performance::descent_rate_standard
		  , &Performance::descent_rate_reduced
		  , &Performance::descent_rate_maximum 
	);
}


//------------------------------------------------------------------------------
//
//

/*!
 *
 */
double Performance::ceiling() const {
	return _data->maxAltitude;
}

/*!
 *
 */
double Performance::reduced_acceleration_factor() const { 
	return _data->redAccel; 
}
double Performance::maximum_acceleration_factor() const { 
	return _data->maxAccel; 
}
double Performance::reduced_deceleration_factor() const { 
	return _data->redDecel; 
}
double Performance::maximum_deceleration_factor() const { 
	return _data->maxDecel; 
}


/*!
 *
 */
double Performance::acceleration_standard( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getAcceleration );
}

/*!
 *
 */
double Performance::deceleration_standard( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getDeceleration );
}

/*!
 *
 */
double Performance::climb_speed_minimum( double ft ) const {
	return cruise_speed_minimum( ft );
}

/*!
 *
 */
double Performance::climb_speed_standard( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getClimbSpeed );
}

/*!
 *
 */
double Performance::climb_speed_maximum( double ft ) const {
	return cruise_speed_maximum( ft );
}

/*!
 *
 */
double Performance::cruise_speed_minimum( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getStallSpeed );
}

/*!
 *
 */
double Performance::cruise_speed_standard( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getCruiseSpeed );
}

/*!
 * Maximum cruising airspeed at an altitude
 */
double Performance::cruise_speed_maximum( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getMaximumSpeed );
}

/*!
 *
 */
double Performance::descent_speed_minimum( double ft ) const {
	return cruise_speed_minimum( ft );
}

/*!
 *
 */
double Performance::descent_speed_standard( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getDescendSpeed );
}
/*!
 *
 */
double Performance::descent_speed_maximum( double ft ) const {
	return cruise_speed_maximum( ft );
}

/*!
 *
 */
double Performance::climb_rate_reduced( double ft ) const {
	return _data->redClimb * climb_rate_standard( ft );
}
/*!
 *
 */
double Performance::climb_rate_standard( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getClimbRate );
}

/*!
 *
 */
double Performance::climb_rate_accelerated( double ft ) const {
	return _data->accClimb * climb_rate_standard( ft );
}

/*!
 *
 */
double Performance::climb_rate_maximum( double ft ) const {
	return _data->maxClimb * climb_rate_standard( ft );
}

/*!
 *
 */
double Performance::descent_rate_reduced( double ft ) const {
	return _data->redDescend * descent_rate_standard( ft );
}

/*!
 *
 */
double Performance::descent_rate_standard( double ft ) const {
	return calc_level_value( ft, find_level_data( ft ), &AltitudeData::getDescendRate );
}

/*!
 *
 */
double Performance::descent_rate_accelerated( double ft ) const {
	return _data->accDescend * descent_rate_standard( ft );
}

/*!
 *
 */
double Performance::descent_rate_maximum( double ft ) const {
	return _data->maxDescend * descent_rate_standard( ft );
}


/*!
 *
 */
std::pair< double, double > 
Performance::time_distance( double ft0, double ft1 ) const {
    throw;
}

//------------------------------------------------------------------------------
// [private]
//

/*!
 *
 */
AltLevelPair Performance::find_level_data( double z ) const {
	AltData &data = *(_data->levelData);
	AltDataIt it = data.begin();

	if ( z < it->level ) return std::make_pair( &(*it), &(*it) );
	
	while ( ++it != data.end() )
		if ( z < it->level ) return std::make_pair( &*(it - 1), &(*it) );
		
	return std::make_pair( &*(it - 1), &*(it - 1) );

}


/*!
 * Retrieve altitude layer value
 *
 * @param z altitude in feet
 * @param altData data at bottom and top of level band
 * @param double ( AltData::* )() const function to retrieve value of interest
 */
double Performance::calc_level_value( 
		double z,
		AltLevelPair altData,
		double ( AltitudeData::*value )() const
) const {	
	if ( altData.first == altData.second ) {
		//const AltitudeData *ad = altData.first;
		return (altData.first->*value)();
	}

    double lAlt = altData.first->level;
    double hAlt = altData.second->level;
    double lVal = (altData.first->*value)();
    double hVal = (altData.second->*value)();

    double alpha = ( z - lAlt ) / ( hAlt - lAlt );

    return ( 1.0 - alpha ) * lVal + alpha * hVal;


}

/*!
 *
 */
double Performance::adjusted_level_value( 
		double z, double t 
	  , double ( Performance::*standard_value )( double ) const
	  , double ( Performance::*minimum_value )( double ) const
	  , double ( Performance::*maximum_value )( double ) const
) const {
	double std = ((*this).*(standard_value))( z );
	if ( 0 == (int)(t * 1000) ) {
		return std;
	}
	else if ( t > 0.0 ) {
		return std + t * ( ((*this).*(maximum_value))( z ) - std );
	}
	else {
		return std - t * ( ((*this).*(minimum_value))( z ) - std );
	}
}


/*!
 *
 */
double Performance::adjusted_value( 
		double t, double std, double min, double max 
) const {
	if      ( 0 == (int)(t * 1000) ) { return std; }
	else if ( t > 0.0 )              { return std + t * ( max - std ); }
	else                             { return std - t * ( min - std ); }
}

////////////////////////////////////////////////////////////////////////////////
